<!DOCTYPE html>
<!--
Encodes two RGB frames and checks that output color space is changed to Rec601.
Then encodes two I420 frame w/ Rec709 colors and checks that the output color
space is changed to Rec709, and that a key frame is generated alongside the
change.

The spec doesn't mandate any particular output color space, so this just checks
that our internal behvaior is surfaced correctly. A key frame is expected when
color space changes to mirror the our decoder requirement to provide a key frame
whenever VideoDecoderConfig changes.
-->
<html>

<head>
  <title>Encode color space test</title>
  <script src="webcodecs_common.js"></script>
  <script type="text/javascript">
    'use strict';

    // Use 16x16 aligned resolution since some platforms require that.
    // See https://crbug.com/1084702.
    // Also, some platforms require a resolution that isn't tiny (e.g. 160) to
    // use hardware acceleration.
    const FRAME_WIDTH = 640;
    const FRAME_HEIGHT = 480;

    function isRec709(colorSpace) {
      return colorSpace.primaries === 'bt709'
        && colorSpace.transfer === 'bt709'
        && colorSpace.matrix === 'bt709'
        && colorSpace.fullRange === false;
    }

    function isSRGB(colorSpace) {
      return colorSpace.primaries === 'bt709'
        && colorSpace.transfer === 'iec61966-2-1'
        && colorSpace.matrix === 'rgb'
        && colorSpace.fullRange === true;
    }

    function isRec601(colorSpace) {
      return colorSpace.primaries === 'smpte170m'
        && colorSpace.transfer === 'smpte170m'
        && colorSpace.matrix === 'smpte170m'
        && colorSpace.fullRange === false;
    }

    function makePixelArray(byteLength) {
      let data = new Uint8Array(byteLength);
      for (let i = 0; i < byteLength; i++) {
        data[i] = i;
      }
      return data;
    }

    function makeFrame(type, timestamp) {
      let init = {
        format: 'RGBA',
        timestamp: timestamp,
        codedWidth: FRAME_WIDTH,
        codedHeight: FRAME_HEIGHT
      };
      switch (type) {
        case 'I420': {
          const yuvByteLength = 1.5 * FRAME_WIDTH * FRAME_HEIGHT;
          let data = makePixelArray(yuvByteLength);
          return new VideoFrame(data, {...init, format: 'I420'});
        }
        case 'RGBA': {
          const rgbaByteLength = 4 * FRAME_WIDTH * FRAME_HEIGHT;
          let data = makePixelArray(rgbaByteLength);
          return new VideoFrame(data, {...init, format: 'RGBA'});
        }
      }
    }

    async function main(arg) {
      const encoderConfig = {
        codec: arg.codec,
        hardwareAcceleration: arg.acceleration,
        width: FRAME_WIDTH,
        height: FRAME_HEIGHT,
      };

      let support = await VideoEncoder.isConfigSupported(encoderConfig);
      if (!support.supported) {
        TEST.skip('Unsupported codec: ' + arg.codec);
        return;
      }

      const frameDuration = 16666;
      let inputFrames = [makeFrame('RGBA', 0 * frameDuration),
                         makeFrame('RGBA', 1 * frameDuration),
                         makeFrame('I420', 2 * frameDuration),
                         makeFrame('I420', 3 * frameDuration)];
      let outputChunks = [];
      let outputMetadatas = [];
      let errors = 0;

      const init = {
        output(chunk, metadata) {
          outputChunks.push(chunk);
          outputMetadatas.push(metadata);
        },
        error(e) {
          errors++;
          TEST.log(e);
        }
      };

      let encoder = new VideoEncoder(init);
      encoder.configure(encoderConfig);

      for (let frame of inputFrames) {
        encoder.encode(frame);
        await waitForNextFrame();
      }
      await encoder.flush();
      encoder.close();

      TEST.assert(errors == 0, 'Encoding errors occurred during the test');
      TEST.assert(outputChunks.length == 4, 'Unexpected number of outputs');
      TEST.assert(outputMetadatas.length == 4, 'Unexpected number of output metadatas');

      // First output should be a key frame and have acompanying metadata.
      // Corresponding input RGBA sRGB, so we expect encoder will have used
      // libYUV to convert to I420 rec601.
      TEST.assert(inputFrames[0].format == 'RGBA', 'inputs[0] is RGBA');
      TEST.assert(isSRGB(inputFrames[0].colorSpace), 'inputs[0] is sRGB');
      TEST.assert(outputChunks[0].type == 'key', 'outputs[0] is key');
      TEST.assert('decoderConfig' in outputMetadatas[0], 'metdatas[0] has decoderConfig');
      TEST.assert(isRec601(outputMetadatas[0].decoderConfig.colorSpace), 'metdatas[0] is rec601');

      // Second outupt may or may not be a key frame w/ metadata (up to
      // encoder). Corresponding input is still RGBA sRGB, so if metadata is
      // given, we expect same colorSpace as for the previous frame.
      TEST.assert(inputFrames[1].format == 'RGBA', 'inputs[1] is RGBA');
      TEST.assert(isSRGB(inputFrames[1].colorSpace), 'inputs[1] is sRGB');
      if('decoderConfig' in outputMetadatas[1]) {
        TEST.assert(isRec601(outputMetadatas[1].decoderConfig.colorSpace), 'metdatas[1] is rec601');
      }

      // Third output should be a key frame and have acompanying metadata
      // because the correpsonding input format changed to I420, which means
      // we passthrough without libYUV conversion and a new decoderConfig
      // should reflect that.
      TEST.assert(inputFrames[2].format == 'I420', 'inputs[2] is I420');
      TEST.assert(isRec709(inputFrames[2].colorSpace), 'inputs[2] is rec709');
      // TODO(https://crbug.com/1241448): Uncomment the line below once android
      // reliably produces a key frame at the appropriate time. For now this
      // is covered by a DCHECK (excluding android) in video_encoder.cc.
      // TEST.assert(outputChunks[2].type == 'key', 'outputs[2] is key');
      TEST.assert('decoderConfig' in outputMetadatas[2], 'metadatas[2] has decoderConfig');
      TEST.assert(isRec709(outputMetadatas[2].decoderConfig.colorSpace), 'metadatas[2] is rec709');

      // Fourth outupt may or may not be a key frame w/ metadata (up to
      // encoder). Corresponding input is still I420 rec709, so if metadata is
      // given, we expect same colorSpace as for the previous frame.
      TEST.assert(inputFrames[3].format == 'I420', 'inputs[3] is I420');
      TEST.assert(isRec709(inputFrames[3].colorSpace, 'inputs[3] is rec709'));
      if('decoderConfig' in outputMetadatas[3]) {
        TEST.assert(isRec709(outputMetadatas[3].decoderConfig.colorSpace), 'metadatas[3] is rec709');
      }
    }
  </script>
</head>

<body>
</body>

</html>
